Skip to content

feat(image-editor): wire Save and focal-point persistence for the new image editor#36350

Queued
oidacra wants to merge 20 commits into
mainfrom
issue-36067-wire-image-editor-save
Queued

feat(image-editor): wire Save and focal-point persistence for the new image editor#36350
oidacra wants to merge 20 commits into
mainfrom
issue-36067-wire-image-editor-save

Conversation

@oidacra

@oidacra oidacra commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

Wires the new Angular image editor's Save to the temp-file flow and completes
focal-point persistence for the new Edit Content.

On Save, the editor GETs the contentAsset filter URL it already builds for preview, with
binaryFieldId=<fieldVar>&_imageToolSaveFile=true appended; the servlet stages the filtered
render as a DotCMSTempFile, the dialog closes returning it, and the field stages it (preview
refreshes; the temp id is sent as the field value on check-in — the source binary is never
overwritten). A focal point is folded into the same request (/filter/FocalPoint/fp/x,y +
overwrite=true) and committed via copyMetadata; the editor seeds the marker from the asset's
stored focal on open and treats a focal-only change as dirty.

Closes #36067.

What changed

Image editor (libs/image-editor) — new withSave feature + saveRequested/saveSucceeded/saveFailed
events + saveEditedImage GET; buildSaveUrl reuses the preview filter chain, appends the save
flags, and folds in the focal point (epsilon-compared against the seeded baseline — recenter-to-clear
and untouched-seed both handled); the dialog closes with the temp on success and stays open + surfaces
the error on failure; focal seeded on open from ImageEditorOpenParams.focalPoint; isDirty widened to
detect a focal move (epsilon-tolerant); footer Save button (+ i18n) shows a loading spinner while the
editor is busy applying a filter or saving.

Binary field (libs/edit-content)onEditImage reads the asset's stored focal
({field}MetaData.focalPoint, via parseFocalPoint) and passes it to the launcher so the marker
re-seeds on reopen; null-metadata guards (handleTempFile, getFileMetadata) so the image-tool temp
(metadata: null) renders instead of crashing.

BackendBinaryExporterServlet re-wraps the temp after copying the rendered bytes so it carries
real metadata (image/mimeType/dimensions) — it was returning metadata:null/image:false/mimeType:unknown,
which left the field showing a file icon — mirroring TempFileAPI.createTempFile;
DefaultTransformStrategy.addBinaries exposes focalPoint on the content REST read path (mirrors
BinaryViewStrategy) so the editor can re-seed.

Acceptance criteria

Test plan

Unit: buildSaveUrl (filters, focal seed-vs-centre, recenter-to-clear, epsilon), withSave (status
transitions + the built Save URL), footer Save (click, loading spinner on busy/saving), dialog
close-on-success / no-close-on-failure, focal seeding, isDirty (epsilon), parseFocalPoint,
null-metadata guards, DefaultTransformStrategy focalPoint — 334 (image-editor) + 1980 (edit-content)
passing
, lint clean, backend compiles.

Manual (local, FEATURE_FLAG_NEW_IMAGE_EDITOR=true): open the editor on a Binary field → apply a filter /
move the focal point → Save → the dialog closes; after Publish the field shows the edited image;
reopen the editor → the focal marker is restored.

Notes

oidacra added 3 commits June 29, 2026 14:49
Add a withSave feature (save events + saveEditedImage GET) that builds the
contentAsset filter URL with _imageToolSaveFile=true, closes the dialog with the
returned temp file on success and surfaces the error on failure. Fold the focal
point into the save chain (epsilon-compared against the seeded baseline), seed it
on open from ImageEditorOpenParams, widen isDirty to detect a focal move, and add a
footer Save button that shows a loading spinner while busy or saving.
…p in binary field

onEditImage reads the asset's stored focal point ({field}MetaData.focalPoint) and
passes it to the launcher so the marker re-seeds on reopen. Guard the null metadata
that image-tool temp files return so the field renders instead of crashing.
…lPoint on read

The _imageToolSaveFile save block created the temp from the empty file, so it came
back with metadata:null/image:false; re-wrap it after copying the rendered bytes so
it carries real metadata (mirrors TempFileAPI.createTempFile). Expose focalPoint on
DefaultTransformStrategy.addBinaries (mirrors BinaryViewStrategy) so the editor can
re-seed the marker on reopen.
@oidacra oidacra force-pushed the issue-36067-wire-image-editor-save branch from 920d5d6 to dedc5f4 Compare June 29, 2026 18:49
@github-actions github-actions Bot added Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code labels Jun 29, 2026
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @adrianjm-dotCMS's task in 3m 4s —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🤖 dotBot Review (Bedrock)

Reviewed 39 file(s); 12 candidate(s) → 9 confirmed, 0 uncertain (unverified, kept for review).

⚠️ Coverage capped: 0 file(s) + 7 lower-severity candidate(s) skipped (limits: 40 files, 12 candidates).

Confirmed findings

  • 🔴 Critical core-web/libs/image-editor/src/lib/store/features/with-save.feature.ts:75 — Incorrect metadata spread leads to loss of existing properties
    The code spreads tempFile.metadata?.metadata (nested 'metadata' property) instead of tempFile.metadata itself. When tempFile.metadata contains direct properties like image/mimeType, they are discarded, causing metadata corruption. This results in focalPoint being the only property, breaking backend expectations for required metadata fields.
  • 🟠 High core-web/libs/dotcms-models/src/lib/shared-models.ts:39 — Premature removal of feature flag breaks gating
    The FEATURE_FLAG_NEW_IMAGE_EDITOR constant was removed (line 39) while the linked issue [IMAGE EDITOR] Add gated 'Edit image' action backed by IMAGE_EDITOR_LAUNCHER #36058 (Enterprise availability gate) remains unimplemented. Grep confirms the flag declaration was deleted without replacement gating in this PR, exposing the feature without license checks or rollout controls.
  • 🟠 High core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.spec.ts:26 — Feature flag check missing in Angular image editor availability
    The isAvailable() method in angular-image-editor.launcher.ts (line 26) no longer checks the FEATURE_FLAG_NEW_IMAGE_EDITOR. Tests validating feature flag gating were removed without corresponding implementation changes, potentially exposing the editor when disabled. Backend enforcement alone leaves UI/UX inconsistency risks.
  • 🟠 High core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.ts:23 — Missing Enterprise license check for image editor
    The PR removed feature flag gating (FEATURE_FLAG_NEW_IMAGE_EDITOR) but didn't implement the required Enterprise license validation as specified in acceptance criteria. Linked issue [IMAGE EDITOR] Wire image editor Save, Download, and Cancel to FileFieldStore temp-file flow #36067 explicitly requires Enterprise license gating, and the PR notes this is deferred to [IMAGE EDITOR] Add gated 'Edit image' action backed by IMAGE_EDITOR_LAUNCHER #36058, but the current changes expose the feature without any license enforcement - a security/licensing violation.
  • 🟠 High core-web/libs/image-editor/src/lib/models/image-editor.models.ts:57 — Incorrect filter name casing for focal point
    The frontend uses FilterName 'FocalPoint' with uppercase 'P', but the backend expects 'focalpoint' in lowercase. This casing mismatch would prevent the focal point filter from being recognized and applied correctly by the backend, leading to focal point data not being persisted.
  • 🟠 High core-web/libs/image-editor/src/lib/services/dot-image-editor.service.ts:140 — GET request used for state-changing operation
    The code uses an HTTP GET request in saveEditedImage() to trigger image saving via this.http.get<DotCMSTempFile>(url). GET requests should not modify server state as they are not idempotent and can be cached/replayed. This violates REST conventions and introduces security risks (CSRF via URL parameters) and potential duplicate temp file creation on retries. The linked issue [IMAGE EDITOR] Wire image editor Save, Download, and Cancel to FileFieldStore temp-file flow #36067 specifically required a POST endpoint for this operation.
  • 🟡 Medium core-web/libs/edit-content/src/lib/fields/dot-edit-content-calendar-field/components/calendar-field/calendar-field.util.spec.ts:583 — Incorrect timezone handling in day difference test
    The test calculates expected day difference using local timezone dates, while the function under test may use server timezone (UTC). This can lead to incorrect test results when server and local timezones differ. For example, dates spanning local midnight but within the same UTC day would show a false failure. Tests should use UTC dates or mock server timezone to ensure consistency.
  • 🟡 Medium core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/focal-point.util.ts:17 — Incorrect parsing of malformed focal point strings
    The parseFocalPoint function splits the input string by commas but does not validate that exactly two components exist. This could lead to silent acceptance of malformed values like '1,2,3' (parsed as x=1, y=2) instead of rejecting invalid formats. The code should check parts.length === 2 before processing coordinates.
  • 🟡 Medium core-web/libs/image-editor/src/lib/components/dot-image-editor/dot-image-editor.component.html:13 — Missing i18n for error message
    The error message displayed in dot-image-editor.component.html line 13 uses store.saveError() which appears to return raw error text without internationalization handling. The <dot-message> component's message input is bound directly to this dynamic error string, suggesting untranslated content would be shown to users. This violates localization requirements for user-facing text.

us.deepseek.r1-v1:0 · Run: #28549515232 · tokens: in: 185477 · out: 46817 · total: 232294 · calls: 60 · est. ~$0.503

Comment thread core-web/libs/image-editor/src/lib/models/image-editor.models.ts
…ata on open

onEditImage called getFileMetadata(this.contentlet), but the raw @input contentlet
carries no fieldVariable, so it could not resolve {field}MetaData and returned {} —
dropping the stored focalPoint. Pass fieldVariable explicitly (as the preview does)
so the editor seeds the saved focal point instead of resetting to centre.
- Make the focal target dot red (var(--p-red-500)) instead of the UI primary blue
  so it reads as a focal marker and contrasts against arbitrary image content.
- Observe the stage (the image's offsetParent) in addition to the image: toggling
  full-screen re-centres the image without changing its own size, which a
  ResizeObserver on the image alone never reports, leaving imageRect.x/y stale and
  the focal/crop overlays mispositioned on the full-screen -> windowed transition.
Comment thread core-web/libs/image-editor/src/lib/models/image-editor.models.ts
Comment thread core-web/libs/image-editor/src/lib/store/features/with-save.feature.ts Outdated
- DefaultTransformStrategy: log (debug) when reading the focal point custom-meta
  fails instead of silently swallowing it in the Try.
- binary-field-utils.spec: split the focal-point sentinel test so the (0,0) case and
  the malformed/single-value case are asserted separately (the latter hits the NaN
  branch, not the (0,0) sentinel).
- onEditImage: document why focalPoint is read via a narrow cast (it is exposed at
  runtime but not declared on the shared DotFileMetadata model).
Comment thread dotCMS/src/main/java/com/dotcms/featureflag/FeatureFlagName.java
…Content

Removing FEATURE_FLAG_NEW_IMAGE_EDITOR made the new Edit Content always open the
new Angular image editor, so the binary-field E2E that waited for the legacy Dojo
iframe (legacy-image-editor-iframe) timed out. Assert the new editor's dialog
(image-editor-root) instead. The legacy-editor describe block (legacy Edit Content,
CONTENT_EDITOR2_ENABLED=false) is unchanged and still asserts the Dojo editor.
Comment thread core-web/libs/dotcms-models/src/lib/dot-contentlets-events.model.ts
Comment thread dotCMS/src/main/java/com/dotcms/featureflag/FeatureFlagName.java
…' test

The calendar-field 'now' TIME default test compared getDate() day-of-month values
directly (Math.abs(actualDay - expectedDay) <= 2). Across a month boundary the
timezone-shifted UTC formValue rolls to the next month, so e.g. Jun 30 vs Jul 1
reads as |30 - 1| = 29 and fails — deterministically on month-end days (today is
Jun 30). Compare the whole-day distance between the two dates instead, which is
correct across month boundaries. Unrelated to the image-editor work; surfaced on
this PR's CI run.
Comment thread core-web/libs/dotcms-models/src/lib/shared-models.ts
@oidacra oidacra added this pull request to the merge queue Jun 30, 2026
Any commits made after this event will not be merged.
@mergify

mergify Bot commented Jun 30, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@erickgonzalez erickgonzalez removed this pull request from the merge queue due to a manual request Jun 30, 2026
Updated the coalesceHistory function to allow discrete actions (e.g., flips) to create individual history entries by passing a null coalesceKey. This change ensures that multiple flips of the same axis are recorded as separate undoable steps. Additionally, modified related tests and documentation to reflect the new behavior, ensuring legacy parity where a crop retains the resize state. Adjusted various tests to align with these changes, enhancing clarity and consistency in the image editor's functionality.
Comment thread core-web/libs/dotcms-models/src/lib/shared-models.ts
Comment thread core-web/libs/image-editor/src/lib/models/image-editor.models.ts
@adrianjm-dotCMS adrianjm-dotCMS added this pull request to the merge queue Jul 1, 2026
Any commits made after this event will not be merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[IMAGE EDITOR] Wire image editor Save, Download, and Cancel to FileFieldStore temp-file flow

2 participants